延續昨日的主題,我們用Javascript搭配Bootstrap,完成了Modal模組化實做Alert Modal(警告視窗),為了延伸更多的互動,今天再來嘗試做一個Confirm Modal(確認視窗)。
先來看一下完成的結果:
這個是一個填寫表單的功能,當使用者按下「填寫表單」按鈕會跳出視窗進行填寫,填寫完畢按下送出前,會再跳出一個視窗確認是否送出,等待所有程序完成,才會把資料送出並關閉所有視窗。
首先我們建立一個新的Contact元件(Component),路徑會放在src/pages下,並記得到src/routes/Route.js裡設定路徑:
src/pages/Contact.js
export const Contact = {
render: () => {
},
}
然後開頭引入會用到的模組,把UI放入render中,等等後面會使用到:
src/pages/Contact.js
//引入jQuery
import $ from 'jquery'
//引入外框組件
import { App } from './App'
//引入Modal模組
import { Modal } from '../components/Modal'
//引入fontawesome
import fontawesome from '@fortawesome/fontawesome'
import { faComment } from '@fortawesome/fontawesome-free-regular'
fontawesome.library.add(faComment)
export const Contact = {
render: () => {
const content = `
<div class="container">
<h1>填寫表單</h1>
<h6>請點擊下方按鈕填寫表單</h6>
<button class="btn btn-danger" id="button"><i class="far fa-comment"></i> 填寫表單</button>
</div>
`
return App.render(content)
},
}
UI準備好了,接下來依序來撰寫功能,先列出功能清單:
清楚流程後,先對按鈕新增click監聽事件,前面我們有實做在Component加入監聽的方法,在Contact物件中新增一個listener屬性,運用Event Delegation對事件的冒泡階段來執行:
src/pages/Contact.js
export const Contact = {
listener: {
click: function (e) {
if (
e.target.closest('button') &&
e.target.closest('button').id === 'button'
) {
//呼叫modal
}
},
},
render: () => {
//...
PS.若不使用上面的方式綁定監聽事件,也可以用mount方法直接對按鈕綁定onclick事件:
src/pages/Contact.js
export const Contact = {
mount: () => {
document.querySelector('#button').onclick = () => {
//呼叫modal
}
},
//...
接著要建立第一個彈出的表單視窗,我們先宣告要傳入Modal函式模組的參數。在作用範圍內宣告formModal,作為表單的UI,因為要在表單送出前彈出確認視窗,所以又宣告了一個formCB,作為彈出表單後的回呼函式:
src/pages/Contact.js
export const Contact = {
listener: {
click: function (e) {
if (
e.target.closest('button') &&
e.target.closest('button').id === 'button'
) {
//呼叫modal
//表單UI
const formModal = `
<form>
<div class="modal-header">
<h5 class="modal-title">留言給river</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="name">稱呼</label>
<input type="text" class="form-control" id="name" placeholder="請填寫您的稱呼" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" placeholder="請填寫email" required>
</div>
<div class="form-group">
<label for="message">留言內容</label>
<textarea type="email" class="form-control" id="message" placeholder="請填寫內容" required></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary" id="submit">提交</button>
</div>
</form>
`
//彈出表單後的callback function
const formCB = () => {
console.log('form callback')
}
//彈出填寫表單視窗
Modal('formModal', formModal, formCB, true)
}
},
},
render: () => {
//...
最後執行Modal函式模組,參數的定義分別為modal ID、Modal內容、callback及彈出前移除其他Modal (可參考[DAY26]番外篇-用Javascript在SPA中實做Bootstrap Modal)。
先來看一下到目前階段的成果,執行npm run build運行webpack,打開瀏覽器到 localhost/#/contact,剛剛傳入的callback會在彈出視窗後console顯示:
到目前都沒問題! 接著下一步就是要在送出表單時彈出確認視窗,所以針對formCB這個callback function來撰寫:
src/pages/Contact.js
//彈出表單後的callback function
const formCB = () => {
//form表單
const form = document.querySelector('#formModal form')
//綁定表單提交事件
form.onsubmit = (event) => {
//取消提交表單預設事件
event.preventDefault()
}
}
這裡對form綁定onsubmit事件,為了避免在彈出確認視窗時真的送出,以preventDefault來取消預設事件行為。
再來我們要處理第二個彈出的確認視窗,所以會再度用到Modal函式模組。跟上面第一個視窗的流程一樣,宣告並加入確認視窗的UI,以及按下確認的callback:
src/pages/Contact.js
//彈出表單後的callback function
const formCB = () => {
//form表單
const form = document.querySelector('#formModal form')
//綁定表單提交事件
form.onsubmit = (event) => {
//取消提交表單預設事件
event.preventDefault()
//確認視窗內容
const confirmModal = `
<form>
<div class="modal-header">
<h5 class="modal-title">留言給river</h5>
</div>
<div class="modal-body">
<h5>確定要提交嗎?</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">否</button>
<button type="button" class="btn btn-primary" id="confirm">是</button>
</div>
</form>
`
//完成提交表單的callback
const confirmCB = () => {
console.log('confirm callback')
}
//彈出確認視窗(不關閉前個視窗)
Modal('confirmModal', confirmModal, confirmCB, false)
}
}
這裡要注意的是,在呼叫第二個Modal時,要把最後一個參數值設定為false,這樣才不會把前一個表單視窗給移除。
然後繼續完成在confirmCB這個回呼函式,我們對確認按鈕綁定onclick事件,當按下確認後才會正式送出,並關掉所有視窗:
src/pages/Contact.js
//完成提交表單的callback
const confirmCB = () => {
//confirm按鈕
const confirm = document.querySelector('#confirmModal #confirm')
confirm.onclick = () => {
console.log('完成提交!')
//關閉所有modal
$('[data-modal="true"]').modal('hide')
}
}
彈出視窗的程序都處理完成了,來看看測試看看結果如何。
非常好,彈出視窗的流程如同前面規劃,當按下確認後會看到console有完成的紀錄! 不過仔細一看,好像哪裡怪怪的R…
雖然經過我們一番努力之下程式都正確運行,但仔細一看會發現,彈出第二個確認視窗時,背景的圖層順序怪怪的。記得前一篇實做提到Bootstrap Modal的特性嗎?Modal彈出時會在DOM裡的body結尾前插入modal與modal-backdrop這兩個DIV元件,第一個彈出的表單在DOM順序為於第二個彈出的確認視窗之上,確認視窗會疊在上面無誤,但Bootstrap對modal預設的z-index是1050,modal-backdrop則是1040,所以視窗永遠在那層背景之上,這樣感受不到層次。
那麼該怎麼解呢? 我們要回去前一天實做的Modal模組做點小補強,自動判斷彈出視窗的z-index圖層順序,並設定在元件的inline style樣式(請看註解新增部份):
src/components/Modal.js
import $ from 'jquery'
export const Modal = (id, content, callback, removeModal = true) => {
//1.預設處理畫面中原有modal
if (removeModal) {
if (document.querySelectorAll('[data-modal="true"]').length) {
document
.querySelectorAll('[data-modal="true"]')
.forEach((v) => v.remove())
}
}
//新增部份:判斷畫面中是否有modal
let zIndexLevel =
document.querySelectorAll('[data-modal="true"]').length * 10,
zIndexModal = 1050,
zIndexBackdrop = 1040
//2.定義新創建的modal物件
const modal = document.createElement('div')
//賦予modal ID
modal.id = id
//賦予modal class
modal.className = 'modal modal-alert fade'
modal.setAttribute('data-modal', 'true')
//設定modal是否可被使用者取消
modal.setAttribute('data-backdrop', 'static')
//新增部份:處理modal本身的z-index
modal.style.zIndex = zIndexModal + zIndexLevel
modal.innerHTML = `
<div class="modal-dialog">
<div class="modal-content">
${content}
</div>
</div>
`
//3.新增至DOM
document.querySelector('body').appendChild(modal)
//4.執行跳出
$(`#${id}`).modal('show')
//新增部份:處理Backdrop z-index
document.querySelector('.modal-backdrop:last-child').style.zIndex =
zIndexBackdrop + zIndexLevel
//5.callback after showing modal
$(`#${id}`).on('shown.bs.modal', function (e) {
callback ? callback() : null
})
//callback after modal is hidden
$(`#${id}`).on('hidden.bs.modal', function (e) {
document.querySelector(`#${id}`).remove()
})
}
補強之後可以看到執行Modal函式模組時,會自動對modal與modal-backdrop的DIV加入inline style,處理Modal的z-index能夠讓圖層的順序正常顯示,實現了多個視窗彈出效果,以上是今天的實做內容。